import Dep from "./dep.js";

let id = 0

class Watcher{
    constructor(vm,keyOrFn,options = {},cb) {
        this.id = id++ // 唯一ID
        this.vm = vm
        // 如果是一个字符串吗，则包装成一个方法
        if(typeof keyOrFn === 'string'){
            this.getter = function (){
                return vm[keyOrFn]
            }
        }else{
            this.getter = keyOrFn // 这里存放多个dep，一个Watcher对应多个deps
        }
        this.depts = []
        this.deptSet = new Set()
        this.lazy = options.lazy
        // 用作计算属性的缓存，标记是否需要重新计算
        this.dirty = this.lazy
        // 由于watcher会默认执行一次get，会渲染一次页面，但是计算属性不需要一上来就执行渲染
        // 所以这里判断，如果dirty为true，则不执行get，只有当dirty为false的时候才执行get，渲染页面
        this.value = this.dirty ? undefined : this.get()
        // 区分是否为用户自定义watcher
        this.user = options.user
        // 拿到watcher的回调
        this.cb = cb
    }
    evaluate(){
        // 在这个方法中调用get方法，会去执行计算属性的方法
        // 这时get中的this指向的计算属性watcher，同时会这这个计算属性watch加入栈中
        this.value = this.get()
        this.dirty = false
    }
    depend(){
        let i = this.depts.length
        while (i--){
            this.depts[i].depend()
        }
    }
    get(){
        // 在调用getter之前，吧当前的Watcher实例放在Dep全局上
        pushWatcher(this)
        // 调用getter就相当于调用的render函数，调用了render函数就会触发每个属性的get方法
        let value = this.getter.call(this.vm)
        //  调用完getter之后，把Dep.target置为null
        popWatcher()
        //  把计算属性的值赋值给value
        return value
    }
    // watcher也要知道我自己下面有几个dept，所以这里要收集一下
    addDep(dep){
        // 判断dep的id不能重复
        if(!this.deptSet.has(dep.id)){
            this.depts.push(dep)
            this.deptSet.add(dep.id)
            dep.addSubs(this)
        }
    }
    // 更新视图
    update(){
        if(this.lazy){
            this.dirty = true
        }else{
            // 实现异步更新，将多个watcher放在一个队列中，然后写一个异步任务实现异步更新
            queueWatcher(this)
        }
    }
    run(){
        console.log('更新视图')
        let oldVal = this.value
        let newVal = this.getter()
        // 判断是否是用户自定义的watcher
        if(this.user){
            this.cb.call(this.vm,newVal,oldVal)
        }
    }
}

let queue = []
let watchObj = {}
let padding = false
function queueWatcher(watcher){
    if(!watchObj[watcher.id]){
        watchObj[watcher.id] = true
        queue.push(watcher)
        // 执行多次进行一个防抖
        if(!padding){
            // 等待同步任务执行完再执行异步更新
            nextTick(flushSchedulerQueue,0)
            padding = true
        }
    }
}

function flushSchedulerQueue(){
    let flushQueue = queue.slice(0)
    queue = []
    watchObj = {}
    padding = false
    flushQueue.forEach(cb=>cb.run())
}

let callbacks = []
let waiting = false
export function nextTick(cb){
    callbacks.push(cb)
    if(!waiting){
        // setTimeout(()=>{
        //     // 依次执行回调
        //     flushCallback()
        // })

        // 使用 Promise.resolve进行异步更新
        Promise.resolve().then(flushCallback)
        waiting = true
    }
}

Dep.target = null
let stack = []
function pushWatcher(watcher){
    stack.push(watcher)
    Dep.target = watcher
}
function popWatcher(){
    stack.pop()
    Dep.target = stack[stack.length-1]
}

function flushCallback(){
    let cbs = callbacks.slice(0)
    callbacks = []
    waiting = false
    cbs.forEach(cb=>cb())
}

export default Watcher